﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.DirectoryServices;
    using System.Text;
    using System.Threading; 
    #endregion

    [Serializable]
    public class SchemaAttribute : ADBaseObjectProperties, ISchemaAttribute {

        #region Members
        #region Fields
        public static new readonly string[] AttributesToGet; 
        #endregion

        public string AdminDescription { get; private set; }
        public string AdminDisplayName { get; private set; }

        /// <summary>
        /// http://technet.microsoft.com/en-us/library/cc961740.aspx
        /// </summary>
        /// <remarks>
        /// The syntax for an attribute defines the storage representation, byte ordering, and matching rules for comparisons of property types. 
        /// Whether the attribute value must be a string, a number, or a unit of time is also defined. Every attribute of every object is 
        /// associated with exactly one syntax. The syntaxes are not represented as objects in the schema, but they are programmed to be 
        /// understood by Active Directory. The allowable syntaxes in Active Directory are predefined. You cannot add new syntaxes.
        /// 
        /// When you define a new attribute, you must specify both the attributeSyntax and the oMSyntax numbers of the syntax you want for 
        /// the attribute. The attributeSyntax number is an object identifier and oMSyntax number is an integer. The oMSyntax is defined by 
        /// the XOM specification. This model provides a relatively fine-grained definition of syntax. For example, there are distinct oMSyntax 
        /// attributes to distinguish among several types of printable strings, according to factors such as the supported character set and 
        /// whether case is significant. 
        /// 
        /// A complete syntax specification consists of both the attribute-syntax and the oMSyntax . 
        /// Whenever more than one oMSyntax can be used with an attribute-syntax, the correct oMSyntax must be used.
        /// Active Directory does not currently enforce character set restrictions for string syntaxes, so if you use attributes with 
        /// string syntax, use only characters in the standard character set.
        /// 
        /// 
        /// Syntax 1                      attribute syntax   om syntax         Description
        /// Undefined                     2.5.5.0                              Not a legal syntax
        /// Object(DN-DN)                 2.5.5.1            127               The fully qualified name of an object in the directory.
        /// String(Object-Identifier)     2.5.5.2            6                 The object identifier.
        /// Case-Sensitive string         2.5.5.3            27                General String.  Differentiates uppercase and lowercase.
        /// CaseIgnoreString(Teletex)     2.5.5.4            20                Teletex.  Does not differentiate uppercase and lowercase.
        /// String(Printable)             2.5.5.5            19                Printable string.  Case-sensitive.
        /// String(IA5)                   2.5.5.5            22                IA5-String.  Case-sensitive.
        /// String(Numeric)               2.5.5.6            18                A sequence of digits.
        /// Object(DN-Binary)             2.5.5.7            127               A distinguished name plus a binary large object.
        /// Boolean                       2.5.5.8            1                 TRUE or FALSE
        /// Integer                       2.5.5.9            2                 32-bit number
        /// Enumeration                   2.5.5.9            10                Enum
        /// String(Octet)                 2.5.5.10           4                 A string representation of bytes.
        /// String(UTC-Time)              2.5.5.11           23                Universal Time
        /// String(Generalized-Time)      2.5.5.11           24                Generalized Time
        /// String(Unicode)               2.5.5.12           64                Unicode string.
        /// Object(Presentation-Address)  2.5.5.13           127               Presentation address.
        /// Object(DN-String)             2.5.5.14           127               A DN-String plus a Unicode string.
        /// String(NT-Sec-Desc)           2.5.5.15           66                Windows Security descriptor.
        /// LargeInteger                  2.5.5.16           65                A 64-bit number.
        /// String(Sid)                   2.5.5.17           4                 Security identifier (SID).
        /// 
        /// </remarks>
        public string AttributeId { get; private set; }
        public string AttributeSyntax { get; private set; }

        /// <summary>
        /// Determines if an attribute is constructed, not persisted in Active Directory
        /// </summary>
        public bool IsConstructed { get; private set; }

        /// <summary>
        /// True if single-valued, false if multi-valued.
        /// </summary>
        public bool? IsSingleValued { get; private set; }

        /// <summary>
        /// True if a member of the Global Catalog.
        /// </summary>
        public bool? IsMemberOfPartialAttributeSet { get; private set; }

        /// <summary>
        /// The literal name of the attribute in AD.
        /// </summary>
        public string LDAPDisplayName { get; private set; }

        public int? OmSyntax { get; private set; }

        /// <summary>
        /// The lower bound of the allowable range.
        /// </summary>
        public int RangeLower { get; private set; }

        /// <summary>
        /// The upper bound of the allowable range.
        /// </summary>
        public int RangeUpper { get; private set; }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/cc220842%28v=prot.10%29.aspx
        /// Optional.  Specifies an integer value that contains flags that define additional properties of the attribute.
        /// </summary>
        /// <remarks>
        /// Currently the only allowed value is FLAG_ATTR_IS_CRITICAL, 0x00000001.
        /// Specifies that the attribute is not a member of the filtered attribute set even if the fRODCFilteredAttribute flag is set
        /// </remarks>
        public int? SchemaFlagsEx { get; private set; }

        public Guid SchemaIdGuid { get; private set; }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/windows/desktop/ms679765%28v=vs.85%29.aspx
        /// Optional.  Contains a set of flags that specify search and indexing information for an attribute
        /// </summary>
        /// <remarks>
        /// Value            Description
        /// 1 (0x00000001)    IX fATTINDEX
        ///                 Specifies a hint to the DC to create an index for the attribute.
        /// 
        /// 2 (0x00000002)    PI fPDNTATTINDEX
        ///                 Specifies a hint to the DC to create an index for the container and the attribute. 
        /// 
        /// 4 (0x00000004)    AR fANR
        ///                 Add this attribute to the Ambiguous Name Resolution (ANR) set. 
        ///                 This is used to assist in finding an object when only partial information is given. 
        ///                 For example, if the LDAP filter is (ANR=JEFF), the search will find each object where 
        ///                 the first name, last name, email address, or other ANR attribute is equal to JEFF. 
        ///                 Bit 0 must be set for this index take affect.
        ///                 
        /// 8 (0x00000008)    PR fPRESERVEONDELETE
        ///                 Specifies that the attribute MUST be preserved on objects after deletion of the object 
        ///                 (that is, when the object is transformed to a tombstone, deleted-object, or recycled-object). 
        ///                 This flag is ignored on link attributes, objectCategory, and sAMAccountType. 
        /// 
        /// 16 (0x00000010)    CP fCOPY
        ///                 Specifies a hint to LDAP clients that the attribute is intended to be copied when copying the object.
        ///                 This flag is not interpreted by the server. 
        /// 
        /// 32 (0x00000020)    TP fTUPLEINDEX
        ///                 Windows Server 2003 and higher. Create a tuple index for the attribute. 
        ///                 This will improve searches where the wildcard appears at the front of the search string. 
        ///                 For example, (sn=*mith).    
        ///                 
        /// 64 (0x00000040)    ST fSUBTREEATTINDEX
        ///                 Supported beginning with ADAM. Creates an index to improve VLV performance on arbitrary attributes.
        ///                 
        /// 128    (0x00000080)CF fCONFIDENTIAL
        ///                 Confidential Attribute (Windows Server 2003 SP1 and higher)
        ///                 Specifies that the attribute is confidential. 
        ///                 An extended access check (section 3.1.1.4.4) is required.
        /// 
        /// 256    (0x00000100)NV fNEVERVALUEAUDIT
        ///                 Disable security auditing for attribute (Windows Server 2008 and higher)
        ///                 Specifies that auditing of changes to individual values contained in this attribute 
        ///                 MUST NOT be performed. Auditing is outside of the state model. 
        /// 
        /// 512    (0x00000200)RO fRODCFilteredAttribute
        ///                 Include attribute in RODC Filtered Attribute Set (FAS) (Windows Server 2008 and higher)
        ///                 Almost ALL attributes, except the Bitlocker attributes, are NOT included in the RODC-FAS. 
        ///                 To prevent replication of the attribute value(s) to RODCs INCLUDE the attribute as a member of the RODC-FAS. 
        ///                 Only attributes that DO NOT have "schemaFlagsEx" with 0x1 can be included in RODC-FAS
        ///                 Filtered Attribute Set is also known as Partial Attribute Set.
        /// </remarks>
        public SearchFlags SearchFlags { get; private set; }
        public bool? SystemOnly { get; private set; }
        #endregion

        #region Constructors
        static SchemaAttribute() {
            AttributesToGet = new string[] {
                "adminDescription", 
                "adminDisplayName", 
                "attributeID",
                "attributeSyntax",
                "isMemberOfPartialAttributeSet", 
                "isSingleValued", 
                "lDAPDisplayName", 
                "objectCategory", 
                "oMSyntax",
                "rangeLower", 
                "rangeUpper", 
                "schemaIDGUID",
                "searchFlags", 
                "showInAdvancedViewOnly",
                "systemOnly"
            };
        }
        public SchemaAttribute() {
            this.Initialize();
        } 

        public SchemaAttribute(DirectoryEntry directoryEntry, bool destroyInputObject = true)
            : base(directoryEntry, destroyInputObject) {
                if (this.GetType().Name == typeof(SchemaAttribute).Name) {
                this.Attributes = null;
            }
        }

        public SchemaAttribute(SearchResult result, bool destroyInputObject = true)
            : base(result, destroyInputObject) {
                if (this.GetType().Name == typeof(SchemaAttribute).Name) {
                this.Attributes = null;
            }
        }

        public SchemaAttribute(List<string> ldifOutput)
            : base(ldifOutput) {
                if (this.GetType().Name == typeof(SchemaAttribute).Name) {
                this.Attributes = null;
            }
        }
        #endregion

        #region Methods
        protected override void Initialize() {
            base.Initialize();

            this.AdminDescription = string.Empty;
            this.AdminDisplayName = string.Empty;
            this.AttributeId = string.Empty;
            this.AttributeSyntax = string.Empty;
            this.LDAPDisplayName = string.Empty;
        }

        protected override void ProcessResults() {

            try {
                base.ProcessResults();

                if (this.Attributes.ContainsKey("adminDescription")) this.AdminDescription = this.Attributes["adminDescription"][0].ToString();
                if (this.Attributes.ContainsKey("adminDisplayName")) this.AdminDisplayName = this.Attributes["adminDisplayName"][0].ToString();
                if (this.Attributes.ContainsKey("attributeId")) this.AttributeId = this.Attributes["attributeId"][0].ToString();
                if (this.Attributes.ContainsKey("attributeSyntax")) this.AttributeSyntax = this.Attributes["attributeSyntax"][0].ToString();
                if (this.Attributes.ContainsKey("isSingleValued")) this.IsSingleValued = Convert.ToBoolean(this.Attributes["isSingleValued"][0]);
                if (this.Attributes.ContainsKey("isMemberOfPartialAttributeSet")) this.IsMemberOfPartialAttributeSet = Convert.ToBoolean(this.Attributes["isMemberOfPartialAttributeSet"][0]);
                if (this.Attributes.ContainsKey("lDAPDisplayName")) {
                    this.LDAPDisplayName = this.Attributes["lDAPDisplayName"][0].ToString();
                    this.Name = this.LDAPDisplayName;
                }
                if (this.Attributes.ContainsKey("oMSynatx")) this.OmSyntax = Convert.ToInt32(this.Attributes["oMSyntax"][0]);
                if (this.Attributes.ContainsKey("rangeLower")) this.RangeLower = Convert.ToInt32(this.Attributes["rangeLower"][0]);
                if (this.Attributes.ContainsKey("rangeUpper")) this.RangeUpper = Convert.ToInt32(this.Attributes["rangeUpper"][0]);
                if (this.Attributes.ContainsKey("schemaIDGUID")) this.SchemaIdGuid = new Guid((byte[])this.Attributes["schemaIDGUID"][0]);
                if (this.Attributes.ContainsKey("searchFlags")) this.SearchFlags = (SearchFlags)this.Attributes["searchFlags"][0];
                if (this.Attributes.ContainsKey("showInAdvancedViewOnly")) this.ShowInAdvancedViewOnly = Convert.ToBoolean(this.Attributes["showInAdvancedViewOnly"][0]);
                if (this.Attributes.ContainsKey("systemOnly")) this.SystemOnly = Convert.ToBoolean(this.Attributes["systemOnly"][0]);

                this.IsConstructed = this.SystemFlags.HasFlag(SystemFlags.IsConstructed);

                this.IsValid = !string.IsNullOrWhiteSpace(this.ObjectCategory) &&
                    this.ObjectCategory.StartsWith("CN=Attribute-Schema,", StringComparison.OrdinalIgnoreCase) &&
                    !string.IsNullOrWhiteSpace(this.LDAPDisplayName);
            }
            catch (Exception e) {
                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Error: {3}", 
                    DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), e.VerboseExceptionString()));
                throw;
            }
        }

        public override string ToString() {
            var info = new StringBuilder();

            info.AppendFormat("LDAPDisplayName: {0}; ", this.LDAPDisplayName ?? "NULL");
            info.AppendFormat("AdminDisplayName: {0}; ", this.AdminDisplayName ?? "NULL");
            info.AppendFormat("AdminDescription: {0}; ", this.AdminDescription ?? "NULL");
            info.AppendFormat("IsSingleValued: {0}; ", this.IsSingleValued.HasValue ? this.IsSingleValued.Value.ToString() : "NULL");
            info.AppendFormat("IsMemberOfPartialAttributeSet: {0}; ", this.IsMemberOfPartialAttributeSet.HasValue ? this.IsMemberOfPartialAttributeSet.Value.ToString() : "NULL");
            info.AppendFormat("SearchFlags: {0}; ", this.SearchFlags);

            return info.ToString();
        }
        #endregion
    }
}
